# -*- coding:utf-8 -*-
# vim: set et sw=4 ts=4 sts=4 ff=unix fenc=utf8:

"""
@version: 1.0
@author: readerror
@contact: readerror@readerror.cn
@contact1: readerror000@gmail.com
@site: http://www.readerror.cn
@software: PyCharm
@file: client.py
@time: 2017/3/6 11:27
"""

import json
import logging
import time
import requests
import sys

from hashlib import sha512

from . import errors
from . import api

python_version = sys.version_info[0]


class Client(
    api.ZoneApiMixin,
    api.SystemApiMixin,
    api.ClusterApiMixin,
    api.VMApiMixin,
    api.UserApiMixin,
    api.NetworkApiMixin,
    api.ImageApiMixin
):
    def __init__(self, username, password, api_url):
        super(Client, self).__init__()
        self.username = username
        self.password = password
        self.api_url = api_url
        self.__session_uuid = None
        logging.basicConfig(level=logging.DEBUG,
                            format='[%(asctime)s] %(filename)s:%(lineno)d [%(levelname)s] %(message)s',
                            # datefmt='%a, %d %b %Y %H:%M:%S', filename='', filemode='w'
                            )
        logging.getLogger("requests").setLevel(logging.WARNING)
        self.log = logging.getLogger(__name__)

    @property
    def session_uuid(self):
        if self.__session_uuid:
            return self.__session_uuid
        else:
            self.login()
            return self.__session_uuid

    # def __del__(self):
    #     if self.token != '':
    #         self.logout()
    #     for method in [getattr(Client, e) for e in dir(Client) if '__' not in e]:
    #         del method
    #     print(self.__class__.login)

    @staticmethod
    def _unicode4python23(string):
        return string if type(string) == 'unicode' else string.encode('utf8')

    def hash_password(self, password):
        return sha512(self._unicode4python23(password)).hexdigest()

    def login(self):
        rsp = self.log_in_by_account({
            'accountName': self.username,
            'password': self.hash_password(self.password),
        })
        self.__session_uuid = rsp['uuid']
        self.log.info('[ZStack] LogInByAccount API succeed [%s] session uuid: %s' % (
            self.username, self.__session_uuid))
        return rsp

    def logout(self):
        rsp = self.log_out({'sessionUuid': self.session_uuid})
        self.log.info('[ZStack] LogOut API succeed [%s]' % self.username)
        return rsp

    def get_api_result(self, res, api_name=False):
        self.log.debug('[ZStack] [RESTfulAPI] [%s] [result] %s' % (self.username, res))
        if api_name:
            return res
        else:
            try:
                result = [_value for _value in res.values()][0]
                for key in result.keys():
                    if key in ['inventories', 'inventory']:
                        result = result[key]
                        break
                return result
            except IndexError:
                self.log.error('[ZStack] [RESTfulAPI] [%s] %s' % (self.username, res))
                return res
            except AttributeError:
                return res

    @staticmethod
    def _raise_for_status(response, explanation=None):
        """Raises stored :class:`APIError`, if one occurred."""
        try:
            response.raise_for_status()
        except requests.exceptions.HTTPError as e:
            if e.response.status_code == 404:
                raise errors.NotFound(e, response, explanation=explanation)
            raise errors.APIError(e, response, explanation=explanation)

    def error_if_fail(self, rsp):
        res = [value for value in rsp.values()][0]
        if not res.get('success'):
            error = res.get('error')
            self.log.error('[ZStack] [RESTfulAPI] [%s] %s' % (self.username, rsp))
            raise errors.ZStackException('ERROR, %s' % error)

    def _format_result(self, response):
        self._raise_for_status(response)
        rsp_body = response.text
        try:
            return json.loads(rsp_body, encoding='utf8')
        except:
            self.log.error('[ZStack] [RESTfulAPI] %s' % rsp_body)
            raise errors.APIError(rsp_body, response)

    def api_call(self, api_id, api_content, async=True, session=None, relogin=False, timeout=0, period=0.05):
        if session:
            api_content['session'] = {'uuid': session}
        else:
            if relogin:
                self.login()
            api_content['session'] = {'uuid': self.__session_uuid}

        headers = {'Content-Type': 'application/json'}
        api_body = {api_id: api_content}
        self.log.debug('[ZStack] [RESTfulAPI] [%s] start [%s] %s' % (self.username, api_id, api_body))

        if python_version == 2:
            res = requests.post(url=self.api_url, json=api_body, headers=headers)
        elif python_version == 3:
            res = requests.post(url=self.api_url, data=json.dumps(api_body), headers=headers)
        else:
            raise errors.SystemVersion

        if res.status_code != 200:
            raise errors.APIError(res.reason, res)

        rsp_body = self._format_result(res)
        if rsp_body.get('state') == 'Done':
            rsp = json.loads(rsp_body['result'])
            self.error_if_fail(json.loads(rsp_body['result']))
            self.log.info('[ZStack] [RESTfulAPI] [%s] ok [%s] %s' % (self.username, api_id, rsp_body))
            return rsp

        job_uuid = rsp_body['uuid']
        self.log.debug('[ZStack] [RESTfulAPI] [%s] running [%s] %s' % (self.username, api_id, rsp_body))
        if async:
            return {api_id: {'job': rsp_body}}

        return self.wait_mission_complete(job_uuid, timeout=timeout, period=period)

    def query_mission(self, job_uuid):
        res = requests.get(url=''.join([self.api_url, r'/result/', job_uuid]))
        if res.status_code != 200:
            if res.status_code == 404:
                return {'result': 'not find'}
            raise errors.APIError(res.reason, res)

        rsp_body = self._format_result(res)
        if rsp_body.get('state') == 'Done':
            rsp = json.loads(rsp_body['result'])
            self.error_if_fail(json.loads(rsp_body['result']))
            return rsp
        else:
            return rsp_body

    def wait_mission_complete(self, job_uuid, timeout=1200, period=0.05, max_period=5):
        time_start = time.time()
        retry = 0
        while (time.time() - time_start) < timeout or (timeout is 0):
            retry += 1
            period += ((retry * 0.01) if period < max_period else 0)
            res = requests.get(url=''.join([self.api_url, r'/result/', job_uuid]))
            if res.status_code != 200:
                raise errors.APIError(res.reason, res)

            rsp_body = self._format_result(res)
            if rsp_body.get('state') == 'Done':
                rsp = json.loads(rsp_body['result'])
                self.error_if_fail(json.loads(rsp_body['result']))
                self.log.info('[ZStack] [RESTfulAPI] Job[uuid:%s] ok (retry:%d, period:%f) %s' %
                              (job_uuid, retry, period, rsp_body))
                return rsp
            time.sleep(period)
            self.log.debug('[ZStack] [RESTfulAPI] Job[uuid:%s] is still in processing (retry:%d, period:%f)' %
                           (job_uuid, retry, period))
